package cn.gson.querydsl.plugin;

import cn.gson.querydsl.config.HibernateConfiguration;
import cn.gson.querydsl.config.SessionFactoryInvocationHandler;
import cn.gson.querydsl.transaction.InjectDsl;
import com.blazebit.persistence.ConfigurationProperties;
import com.blazebit.persistence.Criteria;
import com.blazebit.persistence.CriteriaBuilderFactory;
import com.blazebit.persistence.spi.CriteriaBuilderConfiguration;
import com.querydsl.core.util.StringUtils;
import com.querydsl.jpa.JPQLQueryFactory;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.SessionFactory;
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
import org.hibernate.cfg.AvailableSettings;
import org.noear.solon.Solon;
import org.noear.solon.core.BeanInjector;
import org.noear.solon.core.Props;
import org.noear.solon.core.VarHolder;
import org.noear.solon.core.util.ClassUtil;

import javax.sql.DataSource;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Properties;

/**
 * <p>****************************************************************************</p>
 * <ul style="margin:15px;">
 * <li>Description : querydsl-solon-plugin</li>
 * <li>Version     : 1.0</li>
 * <li>Creation    : 2024年07月20日</li>
 * <li>@author     : ____′↘夏悸</li>
 * </ul>
 * <p>****************************************************************************</p>
 */
@Slf4j
public class QueryFactoryInjector implements BeanInjector<InjectDsl> {

    private JPQLQueryFactory jpqlQueryFactory;

    private BlazeJPAQueryFactory blazeJPAQueryFactory;

    private SessionFactory sessionFactory;

    private CriteriaBuilderFactory criteriaBuilderFactory;

    @Override
    public void doInject(VarHolder varH, InjectDsl anno) {
        Class<?> clz = varH.getType();
        if (clz.isAssignableFrom(JPQLQueryFactory.class)
            || clz.isAssignableFrom(BlazeJPAQueryFactory.class)
            || clz.isAssignableFrom(SessionFactory.class)
        ) {
            varH.context().getWrapAsync(DataSource.class, dataSource -> {
                if (dataSource == null) {
                    throw new IllegalArgumentException("当前 Context 环境中未获取到 DataSource 数据源配置，请检查...");
                }
                if (clz.isAssignableFrom(JPQLQueryFactory.class)) {
                    varH.setValue(getJpqlQueryFactory(dataSource.get()));
                } else if (clz.isAssignableFrom(SessionFactory.class)) {
                    varH.setValue(getSessionFactory(dataSource.get()));
                } else {
                    varH.setValue(getBlazeJPAQueryFactory(dataSource.get()));
                }
            });
        } else {
            throw new IllegalArgumentException("@InjectDsl 只能作用在JPQLQueryFactory或者BlazeJPAQueryFactory,SessionFactory对象上，当前：" + clz.getTypeName());
        }
    }

    public JPQLQueryFactory getJpqlQueryFactory(DataSource dataSource) {
        if (jpqlQueryFactory == null) {
            synchronized (this) {
                SessionFactory managerFactory = getSessionFactory(dataSource);
                this.jpqlQueryFactory = (JPQLQueryFactory) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[]{JPQLQueryFactory.class}, (proxy, method, args) -> {
                    if (method.getName().equals("delete") || method.getName().equals("update") || method.getName().equals("insert")) {
                        JPAQueryFactory jpaQueryFactory = get(JPAQueryFactory.class);
                        if (jpaQueryFactory == null) {
                            EntityManager entityManager = get(EntityManager.class);
                            if (entityManager == null) {
                                entityManager = managerFactory.createEntityManager();
                            }
                            jpaQueryFactory = new JPAQueryFactory(entityManager);
                            SessionFactoryInvocationHandler.threadLocal.get().add(jpaQueryFactory);
                        }
                        return method.invoke(jpaQueryFactory, args);
                    }
                    return method.invoke(new JPAQueryFactory(managerFactory.createEntityManager()), args);
                });
            }
        }
        return jpqlQueryFactory;
    }

    public synchronized BlazeJPAQueryFactory getBlazeJPAQueryFactory(DataSource dataSource) {
        if (blazeJPAQueryFactory == null) {
            synchronized (this) {
                SessionFactory factory = getSessionFactory(dataSource);
                blazeJPAQueryFactory = new BlazeJPAQueryFactory(factory, this.criteriaBuilderFactory);
            }
        }
        return blazeJPAQueryFactory;
    }

    public SessionFactory getSessionFactory(DataSource dataSource) {
        if (this.sessionFactory == null) {
            synchronized (this) {
                Properties properties = new Properties();
                Props prop = Solon.cfg().getProp("jpa.properties");
                if (prop != null) {
                    properties.putAll(prop);
                }
                properties.put(AvailableSettings.JAKARTA_JTA_DATASOURCE, dataSource);
                HibernateConfiguration configuration = new HibernateConfiguration();

                // 扫描实体类所在的包
                List<String> basePackage = Solon.cfg().getList("jpa.basePackage");
                if (basePackage == null || basePackage.isEmpty()) {
                    log.warn("jpa.basePackage未设置，Entity可能读取不到，请检查~");
                }
                configuration.addScanPackage(basePackage);
                configuration.setProperties(properties);
                String physicalNamingStrategy = Solon.cfg().get("jpa.physicalNamingStrategy");

                if (!StringUtils.isNullOrEmpty(physicalNamingStrategy)) {
                    PhysicalNamingStrategy namingStrategy = ClassUtil.tryInstance(physicalNamingStrategy);
                    configuration.setPhysicalNamingStrategy(namingStrategy);
                }
                this.sessionFactory = configuration.buildSessionFactory();
                Solon.context().beanRegister(Solon.context().wrap(SessionFactory.class, this.sessionFactory), "sessionFactory", true);
                CriteriaBuilderConfiguration config = Criteria.getDefault();
                config.setProperty(ConfigurationProperties.INLINE_COUNT_QUERY, "false");
                config.setProperty(ConfigurationProperties.IMPLICIT_GROUP_BY_FROM_SELECT, "false");
                config.setProperty(ConfigurationProperties.IMPLICIT_GROUP_BY_FROM_HAVING, "false");
                config.setProperty(ConfigurationProperties.IMPLICIT_GROUP_BY_FROM_ORDER_BY, "false");
                this.criteriaBuilderFactory = config.createCriteriaBuilderFactory(this.sessionFactory);
            }
        }
        return this.sessionFactory;
    }

    private <T> T get(Class<T> clazz) {
        for (Object o : SessionFactoryInvocationHandler.threadLocal.get()) {
            if (clazz.isAssignableFrom(o.getClass()))
                return (T) o;
        }
        return null;
    }
}
